5.05. Преобразование типов и типизация
Преобразование типов и типизация
Типизация — это система правил, по которым язык программирования отслеживает и контролирует типы данных в программе. Она определяет, какие операции можно выполнять с данными, что можно присвоить переменной, когда и как проверяется корректность использования типов. Типизация бывает статической, когда тип переменной известен на этапе компиляции, компилятор проверяет все операции до запуска программы, и если попытаться сложить строку и число без преобразования — возникнет ошибка на этапе компиляции.
Благодаря этому многие ошибки ловятся до запуска. В динамической типизации же тип переменной определяется во время выполнения, и переменная может хранить разные типы в разное время, это более гибко, но ошибки всплывают только во время выполнения, из-за чего с большими проектами работать сложнее.
C# — статически и строго типизированный язык, как и Java. Это означает, что все типы проверяются на этапе компиляции, нельзя присвоить string в int без явного указания и нельзя вызвать метод, которого нет у типа - компилятор не пропустит.
Но всё же типы преобразовать можно.
- Неявное приведение (implicit), происходит автоматически, когда нет риска потери данных.
int a = 100;
long b = a; // OK: int → long (без потерь)
Разрешено, когда целое становится более широким целым, или целое становится числом с плавающей точкой, а также когда производный класс превращается в базовый. Словом, когда действительно ничего не потеряется.
- Явное приведение (explicit) возлагает ответственность на программиста, требует ручного указания в скобках. Используется, когда возможна потеря данных или неоднозначность.
double d = 123.456;
int i = (int)d; // Явное приведение: 123 (дробная часть теряется)
Без (int) получим ошибку компиляции - это используется для узкого приведения (наоборот long в int), приведения между несовместимыми типами, и приведения по иерархии наследования вниз (базовый в производный).
Таким образом, мы знакомимся с ещё несколькими операторами - операторами преобразования типов.
(T) — явное приведение. T - любой тип, допустим (int). Если тип не совпадает — InvalidCastException.
object obj = "Hello";
string s = (string)obj; // Работает
// string n = (string)123; // InvalidCastException
is — безопасная проверка типа.
if (obj is string s)
{
Console.WriteLine(s.Length);
}
Здесь выполняется проверка, можно ли привести объект к типу. Она не выбрасывает исключение, а с C# 7+ позволяет присвоить результат в переменную.
as — безопасное приведение (только для ссылочных типов), возвращает null, если приведение невозможно. Не работает со значимыми типами (но можно с nullable), его можно использовать когда ожидаем, что приведение может не сработать. Выглядит так:
string s = obj as string;
if (s != null)
{
Console.WriteLine(s.Length);
}
Convert — универсальные методы преобразования, поддерживает множество типов и может преобразовывать между несовместимыми (например, bool в int). Но может выбрасывать исключения (FormatException, InvalidCastException) и выполняется медленнее, чем прямое приведение.
string s = "123";
int i = Convert.ToInt32(s);
double d = Convert.ToDouble("3.14");
Parse — выбрасывает исключение при ошибке:
int number = int.Parse("123"); // OK
// int fail = int.Parse("abc"); // FormatException
TryParse — безопасный способ (рекомендуется):
if (int.TryParse("abc", out int result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("Не удалось распарсить");
}
У TryParse нет исключений, и он возвращает bool - успех или нет. И работает быстро, поэтому лучше использовать именно его для работы с пользовательским вводом.
Чтобы проверять типы, можно использоватьь не только is, но и typeof(T) и obj.GetType().
- is проверяет, является ли объект экземпляром типа;
- typeof(T) возвращает Type для типа на этапе компиляции, допустим typeof(string);
- obj.GetType() возвращает Type для объекта во время выполнения.
Пример:
object obj = "Hello";
Console.WriteLine(obj is string); // True
Console.WriteLine(obj.GetType()); // System.String
Console.WriteLine(typeof(string)); // System.String
В C# предусмотрен и ещё один удобный вариант неявной типизации. Это объявление переменных через var, который не отключает типизацию, а лишь позволяет компилятору определить тип на основе правой части. После компиляции var исчезает — остаётся конкретный тип.
Когда тип очевиден и длинный, спокойно используйте слово var. Фактически это статическая типизация, просто без явного написания имени типа. var – неявно типизированная переменная – автоматически определяет тип:
var age = 30; // int
var name = "Alice"; // string
var list = new List<int>(); // List<int>
Для динамической типизации используется слово dynamic. Тип проверяется во время выполнения, а не компиляции. Компилятор не проверяет наличие методов, свойств, операций. Ошибки всплывают только при запуске.
dynamic obj = "Привет";
Console.WriteLine(obj.Length); // OK
obj = 42;
Console.WriteLine(obj + 10); // 52
И получается, что dynamic небезопасный оператор. Его можно использовать при работе с COM-объектами, с динамическими языками, с JSON-объектами, в тестах или скриптах. Словом, это как дополнительный инструмент совместимости - и dynamic не нужно использовать в бизнес-логике, критичных участках, ведь все преимущества статической типизации отключаются.
COM (Component Object Model) — это старая технология Microsoft (с 1990-х), которая позволяет разным приложениям и компонентам взаимодействовать между собой, даже если они написаны на разных языках (например, C++, Visual Basic, Delphi). Это Excel, Outlook, Active Directory, и хоть и устаревшая, но всё ещё использующаяся в корпоративных приложениях с Excel/Word. COM-объект — это компонент Windows, с которым можно взаимодействовать из C#. dynamic подойдёт для него, потому что типы и методы разрешаются в рантайме, а не на этапе компиляции.